/*
  jQuery strings - 0.3
  http://code.google.com/p/jquery-utils/
  
  (c) Maxime Haineault <haineault@gmail.com>
  http://haineault.com   

  MIT License (http://www.opensource.org/licenses/mit-license.php)

  Implementation of Python3K advanced string formatting
  http://www.python.org/dev/peps/pep-3101/

  Documentation: http://code.google.com/p/jquery-utils/wiki/StringFormat
  
*/
(function ($) {
  var strings = {
    strConversion: {
      // tries to translate any objects type into string gracefully
      __repr: function (i) {
        switch (this.__getType(i)) {
        case 'array':
        case 'date':
        case 'number':
          return i.toString();
        case 'object':
          var o = [];
          for (x = 0; x < i.length; i++) {
            o.push(i + ': ' + this.__repr(i[x]));
          }
          return o.join(', ');
        case 'string':
          return i;
        default:
          return i;
        }
      },
      // like typeof but less vague
      __getType: function (i) {
        if (!i || !i.constructor) {
          return typeof (i);
        }
        var match = i.constructor.toString().match(/Array|Number|String|Object|Date/);
        return match && match[0].toLowerCase() || typeof (i);
      },
      //+ Jonas Raoni Soares Silva
      //@ http://jsfromhell.com/string/pad [v1.0]
      __pad: function (str, l, s, t) {
        var p = s || ' ';
        var o = str;
        if (l - str.length > 0) {
          o = new Array(Math.ceil(l / p.length)).join(p).substr(0, t = !t ? l : t == 1 ? 0 : Math.ceil(l / 2)) + str + p.substr(0, l - t);
        }
        return o;
      },
      __getInput: function (arg, args) {
        var key = arg.getKey();
        switch (this.__getType(args)) {
        case 'object':
          // Thanks to Jonathan Works for the patch
          var keys = key.split('.');
          var obj = args;
          for (var subkey = 0; subkey < keys.length; subkey++) {
            obj = obj[keys[subkey]];
          }
          if (typeof (obj) != 'undefined') {
            if (strings.strConversion.__getType(obj) == 'array') {
              return arg.getFormat().match(/\.\*/) && obj[1] || obj;
            }
            return obj;
          } else {
            // TODO: try by numerical index                    
          }
          break;
        case 'array':
          key = parseInt(key, 10);
          if (arg.getFormat().match(/\.\*/) && typeof args[key + 1] != 'undefined') {
            return args[key + 1];
          } else if (typeof args[key] != 'undefined') {
            return args[key];
          } else {
            return key;
          }
          break;
        }
        return '{' + key + '}';
      },
      __formatToken: function (token, args) {
        var arg = new Argument(token, args);
        return strings.strConversion[arg.getFormat().slice(-1)](this.__getInput(arg, args), arg);
      },
      // Signed integer decimal.
      d: function (input, arg) {
        var o = parseInt(input, 10); // enforce base 10
        var p = arg.getPaddingLength();
        if (p) {
          return this.__pad(o.toString(), p, arg.getPaddingString(), 0);
        } else {
          return o;
        }
      },
      // Signed integer decimal.
      i: function (input, args) {
        return this.d(input, args);
      },
      // Unsigned octal
      o: function (input, arg) {
        var o = input.toString(8);
        if (arg.isAlternate()) {
          o = this.__pad(o, o.length + 1, '0', 0);
        }
        return this.__pad(o, arg.getPaddingLength(), arg.getPaddingString(), 0);
      },
      // Unsigned decimal
      u: function (input, args) {
        return Math.abs(this.d(input, args));
      },
      // Unsigned hexadecimal (lowercase)
      x: function (input, arg) {
        var o = parseInt(input, 10).toString(16);
        o = this.__pad(o, arg.getPaddingLength(), arg.getPaddingString(), 0);
        return arg.isAlternate() ? '0x' + o : o;
      },
      // Unsigned hexadecimal (uppercase)
      X: function (input, arg) {
        return this.x(input, arg).toUpperCase();
      },
      // Floating point exponential format (lowercase)
      e: function (input, arg) {
        return parseFloat(input, 10).toExponential(arg.getPrecision());
      },
      // Floating point exponential format (uppercase)
      E: function (input, arg) {
        return this.e(input, arg).toUpperCase();
      },
      // Floating point decimal format
      f: function (input, arg) {
        return this.__pad(parseFloat(input, 10).toFixed(arg.getPrecision()), arg.getPaddingLength(), arg.getPaddingString(), 0);
      },
      // Floating point decimal format (alias)
      F: function (input, args) {
        return this.f(input, args);
      },
      // Floating point format. Uses exponential format if exponent is greater than -4 or less than precision, decimal format otherwise
      g: function (input, arg) {
        var o = parseFloat(input, 10);
        return (o.toString().length > 6) ? Math.round(o.toExponential(arg.getPrecision())) : o;
      },
      // Floating point format. Uses exponential format if exponent is greater than -4 or less than precision, decimal format otherwise
      G: function (input, args) {
        return this.g(input, args);
      },
      // Single character (accepts integer or single character string). 	
      c: function (input, args) {
        var match = input.match(/\w|\d/);
        return match && match[0] || '';
      },
      // String (converts any JavaScript object to anotated format)
      r: function (input, args) {
        return this.__repr(input);
      },
      // String (converts any JavaScript object using object.toString())
      s: function (input, args) {
        return input.toString && input.toString() || '' + input;
      }
    },
    format: function (str, args) {
      var end = 0;
      var start = 0;
      var match = false;
      var buffer = [];
      var token = '';
      var tmp = (str || '').split('');
      for (start = 0; start < tmp.length; start++) {
        if (tmp[start] == '{' && tmp[start + 1] != '{') {
          end = str.indexOf('}', start);
          token = tmp.slice(start + 1, end).join('');
          if (tmp[start - 1] != '{' && tmp[end + 1] != '}') {
            var tokenArgs = (typeof arguments[1] != 'object') ? arguments2Array(arguments, 2) : args || [];
            buffer.push(strings.strConversion.__formatToken(token, tokenArgs));
          } else {
            buffer.push(token);
          }
        } else if (start > end || buffer.length < 1) {
          buffer.push(tmp[start]);
        }
      }
      return (buffer.length > 1) ? buffer.join('') : buffer[0];
    },
    calc: function (str, args) {
      return eval(format(str, args));
    },
    repeat: function (s, n) {
      return new Array(n + 1).join(s);
    },
    UTF8encode: function (s) {
      return unescape(encodeURIComponent(s));
    },
    UTF8decode: function (s) {
      return decodeURIComponent(escape(s));
    },
    tpl: function () {
      var out = '';
      var render = true;
      // Set
      // $.tpl('ui.test', ['<span>', helloWorld ,'</span>']);
      if (arguments.length == 2 && $.isArray(arguments[1])) {
        this[arguments[0]] = arguments[1].join('');
        return $(this[arguments[0]]);
      }
      // $.tpl('ui.test', '<span>hello world</span>');
      if (arguments.length == 2 && $.isString(arguments[1])) {
        this[arguments[0]] = arguments[1];
        return $(this[arguments[0]]);
      }
      // Call
      // $.tpl('ui.test');
      if (arguments.length == 1) {
        return $(this[arguments[0]]);
      }
      // $.tpl('ui.test', false);
      if (arguments.length == 2 && arguments[1] == false) {
        return this[arguments[0]];
      }
      // $.tpl('ui.test', {value:blah});
      if (arguments.length == 2 && $.isObject(arguments[1])) {
        return $($.format(this[arguments[0]], arguments[1]));
      }
      // $.tpl('ui.test', {value:blah}, false);
      if (arguments.length == 3 && $.isObject(arguments[1])) {
        return (arguments[2] == true) ? $.format(this[arguments[0]], arguments[1]) : $($.format(this[arguments[0]], arguments[1]));
      }
    }
  };
  var Argument = function (arg, args) {
      this.__arg = arg;
      this.__args = args;
      this.__max_precision = parseFloat('1.' + (new Array(32)).join('1'), 10).toString().length - 3;
      this.__def_precision = 6;
      this.getString = function () {
        return this.__arg;
      };
      this.getKey = function () {
        return this.__arg.split(':')[0];
      };
      this.getFormat = function () {
        var match = this.getString().split(':');
        return (match && match[1]) ? match[1] : 's';
      };
      this.getPrecision = function () {
        var match = this.getFormat().match(/\.(\d+|\*)/g);
        if (!match) {
          return this.__def_precision;
        } else {
          match = match[0].slice(1);
          if (match != '*') {
            return parseInt(match, 10);
          } else if (strings.strConversion.__getType(this.__args) == 'array') {
            return this.__args[1] && this.__args[0] || this.__def_precision;
          } else if (strings.strConversion.__getType(this.__args) == 'object') {
            return this.__args[this.getKey()] && this.__args[this.getKey()][0] || this.__def_precision;
          } else {
            return this.__def_precision;
          }
        }
      };
      this.getPaddingLength = function () {
        var match = false;
        if (this.isAlternate()) {
          match = this.getString().match(/0?#0?(\d+)/);
          if (match && match[1]) {
            return parseInt(match[1], 10);
          }
        }
        match = this.getString().match(/(0|\.)(\d+|\*)/g);
        return match && parseInt(match[0].slice(1), 10) || 0;
      };
      this.getPaddingString = function () {
        var o = '';
        if (this.isAlternate()) {
          o = ' ';
        }
        // 0 take precedence on alternate format
        if (this.getFormat().match(/#0|0#|^0|\.\d+/)) {
          o = '0';
        }
        return o;
      };
      this.getFlags = function () {
        var match = this.getString().matc(/^(0|\#|\-|\+|\s)+/);
        return match && match[0].split('') || [];
      };
      this.isAlternate = function () {
        return !!this.getFormat().match(/^0?#/);
      };
    };
  var arguments2Array = function (args, shift) {
      var o = [];
      for (l = args.length, x = (shift || 0) - 1; x < l; x++) {
        o.push(args[x]);
      }
      return o;
    };
  $.extend(strings);
})(jQuery);